using System.Collections.Generic;
using OhioState.Graphics;
using Tao.OpenGl;

namespace OhioState.Graphics.OpenGL
{
    class MaterialGL : IMaterial
    {
       #region IMateral properties

       private IRasterOps _rasterOps;
        public IRasterOps Compositor
        {
            get { return _rasterOps; }
            set { _rasterOps = value; }
        }
        private IShaderProgram _shaderProgram;
        public IShaderProgram Shader
        {
            get { return _shaderProgram; }
            set { _shaderProgram = value; }
        }
        private MaterialColors _frontMaterials;
        public MaterialColors FrontMaterial
        {
            get { return _frontMaterials; }
            set { _frontMaterials = value; }
        }
        private MaterialColors _backMaterials;
        public MaterialColors BackMaterial
        {
            get { return _backMaterials; }
            set { _backMaterials = value; }
        }
        private bool _applyLighting = false;
        public bool ApplyLighting
        {
            get { return _applyLighting; }
            set { _applyLighting = value; }
        }
        private OSUColor _currentColor;
        public OSUColor CurrentColor
        {
            get { return _currentColor; }
            set { _currentColor = value; }
        }
        #endregion IMaterial properties

        private List<ITexture> _textureBinding;
        public List<ITexture> TextureBinding
        {
            get { return _textureBinding; }
            set { _textureBinding = value; }
        }

        //debug
        private LightCollectionHandler _lightCollectionHandler;
        public LightCollectionHandler LightCollectionHandler
        {
            get { return _lightCollectionHandler; }
            set { _lightCollectionHandler = value; }
        }
        //end of debug

        public MaterialGL()
        {
            _textureBinding = new List<ITexture>(4);
        }
        //
        // TODO Change this IMaterial to use an IShader interface.
        //
        public void MakeActive(IRenderPanel panel)
        {
            setShaderProgram(panel);
            setColors(panel);
            setRasterOps(panel);
            bindTextureUnits(panel);
        }


        #region OpenGL methods
        private void setColors(IRenderPanel panel)
        {
            if (this.ApplyLighting && _lightCollectionHandler != null)
            {
                //
                // Set up the colors
                //
                OSUColor color = FrontMaterial.AmbientColor;
                float[] colorf = { color.R, color.G, color.B, color.A };
                Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT, colorf);
                color = FrontMaterial.DiffuseColor;
                colorf[0] = color.R; colorf[1] = color.G; colorf[2] = color.B; colorf[3] = color.A;
                Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_DIFFUSE, colorf);
                color = FrontMaterial.SpecularColor;
                colorf[0] = color.R; colorf[1] = color.G; colorf[2] = color.B; colorf[3] = color.A;
                Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_SPECULAR, colorf);
                color = FrontMaterial.EmissiveColor;
                colorf[0] = color.R; colorf[1] = color.G; colorf[2] = color.B; colorf[3] = color.A;
                Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_EMISSION, colorf);
                Gl.glMaterialf(Gl.GL_FRONT, Gl.GL_SHININESS, FrontMaterial.SpecularExponent);

                color = BackMaterial.AmbientColor;
                colorf[0] = color.R; colorf[1] = color.G; colorf[2] = color.B; colorf[3] = color.A;
                Gl.glMaterialfv(Gl.GL_BACK, Gl.GL_AMBIENT, colorf);
                color = BackMaterial.DiffuseColor;
                colorf[0] = color.R; colorf[1] = color.G; colorf[2] = color.B; colorf[3] = color.A;
                Gl.glMaterialfv(Gl.GL_BACK, Gl.GL_DIFFUSE, colorf);
                color = BackMaterial.SpecularColor;
                colorf[0] = color.R; colorf[1] = color.G; colorf[2] = color.B; colorf[3] = color.A;
                Gl.glMaterialfv(Gl.GL_BACK, Gl.GL_SPECULAR, colorf);
                color = BackMaterial.EmissiveColor;
                colorf[0] = color.R; colorf[1] = color.G; colorf[2] = color.B; colorf[3] = color.A;
                Gl.glMaterialfv(Gl.GL_BACK, Gl.GL_EMISSION, colorf);
                Gl.glMaterialf(Gl.GL_BACK, Gl.GL_SHININESS, BackMaterial.SpecularExponent);

                //debug
                //hack!
                //_lightCollectionHandler.SetShaderID(_shaderProgram.Guid);
                
                //
                // TODO: Why do we need a numlights in all shaders?
                //    This can be handled normally with the existing system, allowing a shader
                //    to override the number of lights.
                //
                //we assume that shader program is already linked here...
                //int numLights = _lightCollectionHandler.LightProxy.LightProxies.Count;
                //int location = Gl.glGetUniformLocation((int)_shaderProgram.Guid, "num_lights");
                //Gl.glUniform1i(location, numLights);

                _lightCollectionHandler.Enable(panel);
                //end of debug
            }
            else
            {
                OSUColor color = CurrentColor;
                Gl.glColor4f(color.R, color.G, color.B, color.A);
            }
        }
        private void setRasterOps(IRenderPanel panel)
        {
            if (Compositor.DepthTest == DepthMode.ON)
            {
                Gl.glEnable(Gl.GL_DEPTH_TEST);
                Gl.glDepthMask(Gl.GL_TRUE);
            }
            else if (Compositor.DepthTest == DepthMode.OFF)
            {
                Gl.glDisable(Gl.GL_DEPTH_TEST);
                Gl.glDepthMask(Gl.GL_FALSE);
            }

            if (Compositor.AlphaTest == AlphaMode.OFF)
                Gl.glDisable(Gl.GL_ALPHA_TEST);
            else
            {
                Gl.glEnable(Gl.GL_ALPHA_TEST);
                // TODO - Set the proper function and threshold.
                Gl.glAlphaFunc(Gl.GL_GEQUAL, 0.5f);
            }

            GLBlend.SetBlendFunctionType(Compositor.Blending);
            GLBlend.ApplyBlending();
        }
        private void setShaderProgram(IRenderPanel panel)
        {
            if (_shaderProgram != null)
            {
                _shaderProgram.MakeActive(panel);
            }
        }
        //
        // Enable/Diable textures
        //
        private void bindTextureUnits(IRenderPanel panel)
        {
            int count = 0;
            foreach (ITexture texture in _textureBinding)
            {
                Gl.glActiveTexture(Gl.GL_TEXTURE0 + count);
                texture.bind();
                count++;
            }
        }
        private void unbindTextureUnits(IRenderPanel panel)
        {
            int count = 0;
            foreach (ITexture texture in _textureBinding)
            {
                Gl.glActiveTexture(Gl.GL_TEXTURE0 + count);
                texture.unbind();
                count++;
            }
            Gl.glActiveTexture(Gl.GL_TEXTURE0);
        }
        public void Deactivate(IRenderPanel panel)
        {
            if (_shaderProgram != null)
            {
                _shaderProgram.Deactivate();
            }
            unbindTextureUnits(panel);
            GLBlend.RemoveBlending();
            if (this.ApplyLighting && _lightCollectionHandler != null)
                _lightCollectionHandler.Disable(panel);

            Gl.glDepthMask(Gl.GL_TRUE);
        }


        #endregion OpenGL methods
    }
}
